package com.lai.hospital.service.impl;

import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lai.hospital.config.SmsConfig;
import com.lai.hospital.config.WxConfigProperties;
import com.lai.hospital.constant.CommonConst;
import com.lai.hospital.dao.UserAuthDao;
import com.lai.hospital.dao.UserInfoDao;
import com.lai.hospital.dao.UserRoleDao;
import com.lai.hospital.dto.*;
import com.lai.hospital.entity.UserAuth;
import com.lai.hospital.entity.UserInfo;
import com.lai.hospital.entity.UserRole;
import com.lai.hospital.enums.LoginTypeEnum;
import com.lai.hospital.enums.RoleEnum;
import com.lai.hospital.exception.BizException;
import com.lai.hospital.service.RedisService;
import com.lai.hospital.service.UserAuthService;
import com.lai.hospital.utils.BeanCopyUtils;
import com.lai.hospital.utils.IpUtils;
import com.lai.hospital.utils.PageUtils;
import com.lai.hospital.utils.UserUtils;
import com.lai.hospital.vo.*;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.lai.hospital.constant.CommonConst.*;
import static com.lai.hospital.constant.MQPrefixConst.EMAIL_EXCHANGE;
import static com.lai.hospital.constant.RedisPrefixConst.*;
import static com.lai.hospital.enums.UserAreaTypeEnum.getUserAreaType;
import static com.lai.hospital.utils.CommonUtils.*;


/**
 * 用户账号服务
 *
 * @author laiciqi
 */
@Service
public class UserAuthServiceImpl extends ServiceImpl<UserAuthDao, UserAuth> implements UserAuthService {
    @Autowired
    private RedisService redisService;
    @Autowired
    private UserAuthDao userAuthDao;
    @Autowired
    private UserRoleDao userRoleDao;
    @Autowired
    private UserInfoDao userInfoDao;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private WxConfigProperties wxConfigProperties;
    @Resource
    private HttpServletRequest request;
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    @Autowired
    private SmsConfig smsConfig;
    @Autowired
    private SessionRegistry sessionRegistry;

    @Override
    public void sendCode(String username) {
        // 校验账号是否合法
        if (!checkEmail(username)) {
            throw new BizException("请输入正确邮箱");
        }
        // 生成六位随机验证码发送
        String code = getRandomCode();
        // 发送验证码
        EmailDTO emailDTO = EmailDTO.builder().email(username).subject("验证码").content("您的验证码为 " + code + " 有效期15分钟，请不要告诉他人哦！").build();
        rabbitTemplate.convertAndSend(EMAIL_EXCHANGE, "*", new Message(JSON.toJSONBytes(emailDTO), new MessageProperties()));
        // 将验证码存入redis，设置过期时间为15分钟
        redisService.set(USER_LOGIN_CODE_KEY + username, code, CODE_EXPIRE_TIME);
    }

    @Override
    public void sendPhoneCode(String phone,Integer propose) {
        //检查手机号是否合法
        try {
            if(propose == 0){
                String code = smsConfig.sms(phone,propose);
                // 将验证码存入redis，设置过期时间为15分钟
                redisService.set(USER_LOGIN_CODE_KEY + phone, code, CODE_EXPIRE_TIME);
            }else{
                String code = smsConfig.sms(phone,propose);
                // 将验证码存入redis，设置过期时间为15分钟
                redisService.set(USER_PHONE_CODE_KEY + phone, code, CODE_EXPIRE_TIME);
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    public List<UserAreaDTO> listUserAreas(ConditionVO conditionVO) {
        List<UserAreaDTO> userAreaDTOList = new ArrayList<>();
        switch (Objects.requireNonNull(getUserAreaType(conditionVO.getType()))) {
            case USER:
                // 查询注册用户区域分布
                Object userArea = redisService.get(USER_AREA);
                if (Objects.nonNull(userArea)) {
                    userAreaDTOList = JSON.parseObject(userArea.toString(), List.class);
                }
                return userAreaDTOList;
            case VISITOR:
                // 查询游客区域分布
                Map<String, Object> visitorArea = redisService.hGetAll(VISITOR_AREA);
                if (Objects.nonNull(visitorArea)) {
                    userAreaDTOList = visitorArea.entrySet().stream().map(item -> UserAreaDTO.builder().name(item.getKey()).value(Long.valueOf(item.getValue().toString())).build()).collect(Collectors.toList());
                }
                return userAreaDTOList;
            default:
                break;
        }
        return userAreaDTOList;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void register(UserVO user) {
        // 校验账号是否合法
        if (checkUser(user)) {
            throw new BizException("邮箱已被注册！");
        }
        // 新增用户信息
        UserInfo userInfo = UserInfo.builder()
                .phone(user.getUsername())
                .nickname(CommonConst.DEFAULT_NICKNAME + IdWorker.getId())
                .avatar("https://hospital-1307904337.cos.ap-guangzhou.myqcloud.com/avatar/visitor_avatar.png")
                .build();
        userInfoDao.insert(userInfo);
        // 绑定用户角色
        UserRole userRole = UserRole.builder()
                .userId(userInfo.getId())
                .roleId(RoleEnum.USER.getRoleId())
                .build();
        userRoleDao.insert(userRole);
        // 新增用户账号
        UserAuth userAuth = UserAuth.builder()
                .userInfoId(userInfo.getId())
                .username(user.getUsername())
                .password(BCrypt.hashpw(user.getPassword(), BCrypt.gensalt()))
                .loginType(LoginTypeEnum.EMAIL.getType())
                .build();
        userAuthDao.insert(userAuth);
    }

    @Override
    public void updatePassword(UserVO user) {
        // 校验账号是否合法
        if (!checkUser(user)) {
            throw new BizException("邮箱尚未注册！");
        }
        // 根据用户名修改密码
        userAuthDao.update(new UserAuth(), new LambdaUpdateWrapper<UserAuth>()
                .set(UserAuth::getPassword, BCrypt.hashpw(user.getPassword(), BCrypt.gensalt()))
                .eq(UserAuth::getUsername, user.getUsername()));
    }

    @Override
    public void updateAdminPassword(PasswordVO passwordVO) {
        // 查询旧密码是否正确
        UserAuth user = userAuthDao.selectOne(new LambdaQueryWrapper<UserAuth>()
                .eq(UserAuth::getId, UserUtils.getLoginUser().getId()));
        // 正确则修改密码，错误则提示不正确
        if (Objects.nonNull(user) && BCrypt.checkpw(passwordVO.getOldPassword(), user.getPassword())) {
            UserAuth userAuth = UserAuth.builder()
                    .id(UserUtils.getLoginUser().getId())
                    .password(BCrypt.hashpw(passwordVO.getNewPassword(), BCrypt.gensalt()))
                    .build();
            userAuthDao.updateById(userAuth);
        } else {
            throw new BizException("旧密码不正确");
        }
    }

    @Override
    public PageResult<UserBackDTO> listUserBackDTO(ConditionVO condition) {
        // 获取后台用户数量
        Integer count = userAuthDao.countUser(condition);
        if (count == 0) {
            return new PageResult<>();
        }
        // 获取后台用户列表
        List<UserBackDTO> userBackDTOList = userAuthDao.listUsers(PageUtils.getLimitCurrent(), PageUtils.getSize(), condition);
        return new PageResult<>(userBackDTOList, count);
    }

    @Override
    public WxLoginDTO getWxUserOpenId(String code) {
        String authUrl = wxConfigProperties.getUrl();
        authUrl = authUrl + "?grant_type=" + wxConfigProperties.getGrantType() + "&appid=" + wxConfigProperties.getAppId() +
                "&secret=" + wxConfigProperties.getSecret() + "&js_code=" + code;
        String result = HttpUtil.get(authUrl);
        JSONObject jsonResult = JSONUtil.parseObj(result);
        return WxLoginDTO.builder()
                .openId(jsonResult.getStr("openid"))
                .sessionKey(jsonResult.getStr("session_key"))
                .build();
    }

    @Transactional
    @Override
    public UserInfoDTO wxLogin(WeiXinLoginVO weiXinLoginVO) {
        String ipAddress = IpUtils.getIpAddress(request);
        String ipSource;
        if(ipAddress.equals(IPV6_ADDRESS)){
            ipAddress = LOCAL_IP_ADDRESS;
            ipSource = LOCAL_IP_SOURCE;
        }else{
            ipSource = IpUtils.getIpSource(ipAddress);
        }
        //1、查询数据库中 传入的openid  有则执行查询操作   没有则插入
        UserAuth user = baseMapper.selectOne(new LambdaQueryWrapper<UserAuth>()
                .eq(UserAuth::getUsername, weiXinLoginVO.getOpenId())
                .eq(UserAuth::getLoginType, LoginTypeEnum.WEIXIN.getType()));
        if (Objects.isNull(user)) {//如果查询出来的userAuth为空
            return saveUserInfo(weiXinLoginVO, ipAddress, ipSource);
        } else {
            return updateUserInfo(user, ipAddress, ipSource);
        }
    }


    @Transactional
    @Override
    public UserInfoDTO phoneLogin(UserPhoneLoginVO userPhoneLoginVO) {
        String ipAddress = IpUtils.getIpAddress(request);
        String ipSource;
        if(ipAddress.equals(IPV6_ADDRESS)){
            ipAddress = LOCAL_IP_ADDRESS;
            ipSource = LOCAL_IP_SOURCE;
        }else{
            ipSource = IpUtils.getIpSource(ipAddress);
        }

        //查询数据库中有无此用户
        UserAuth user = checkUser(userPhoneLoginVO);
        if (Objects.isNull(user)) {//为空
            //保存用户信息
            return saveUserInfo(userPhoneLoginVO,ipAddress,ipSource);
        } else {//不为空
            //更新用户信息
            return updateUserInfo(user, ipAddress, ipSource);
        }
    }

    @Override
    public void updatePhone(UserPhoneLoginVO user) {
        if(!user.getCode().equals(redisService.get(USER_PHONE_CODE_KEY + user.getPhone()))){
            throw new BizException("验证码错误！");
        }
        UserAuth userAuth = UserAuth.builder()
                .id(UserUtils.getLoginUser().getId())
                .phone(user.getPhone())
                .build();
        userAuthDao.updateById(userAuth);
        UserInfo userInfo = UserInfo.builder()
                .id(UserUtils.getLoginUser().getUserInfoId())
                .phone(user.getPhone())
                .build();
        userInfoDao.updateById(userInfo);
    }

    /**
     * 校验用户数据是否合法并返回
     *
     * @param user 用户数据
     * @return 结果
     */
    private UserAuth checkUser(UserPhoneLoginVO user) {
        //判断验证码
        if (!user.getCode().equals(redisService.get(USER_LOGIN_CODE_KEY + user.getPhone()))) {
            throw new BizException("验证码错误！");
        }
        //查询用户是否存在
        UserAuth userAuth = userAuthDao.selectOne(new LambdaQueryWrapper<UserAuth>()
                .eq(UserAuth::getPhone, user.getPhone())
                .eq(UserAuth::getLoginType, LoginTypeEnum.PHONE));
        return userAuth;
    }

    /**
     * 校验用户数据是否合法
     *
     * @param user 用户数据
     * @return 结果
     */
    private Boolean checkUser(UserVO user) {
        if (!user.getCode().equals(redisService.get(USER_LOGIN_CODE_KEY + user.getUsername()))) {
            throw new BizException("验证码错误！");
        }
        //查询用户名是否存在
        UserAuth userAuth = userAuthDao.selectOne(new LambdaQueryWrapper<UserAuth>()
                .select(UserAuth::getUsername)
                .eq(UserAuth::getUsername, user.getUsername()));
        return Objects.nonNull(userAuth);
    }

    /**
     * 统计用户地区
     */
    @Scheduled(cron = "0 0 * * * ?")
    public void statisticalUserArea() {
        // 统计用户地域分布
        Map<String, Long> userAreaMap = userAuthDao.selectList(new LambdaQueryWrapper<UserAuth>()
                .select(UserAuth::getIpSource))
                .stream()
                .map(item -> {
            if (StringUtils.isNotBlank(item.getIpSource())) {
                //转换成省 市
                return item.getIpSource().substring(0, 2).replaceAll(PROVINCE, "").replaceAll(CITY, "");
            }
            return UNKNOWN;
        }).collect(Collectors.groupingBy(item -> item, Collectors.counting()));
        // 转换格式
        List<UserAreaDTO> userAreaList = userAreaMap.entrySet().stream()
                .map(item ->
                        //{"name(key)":"上海","value":1}
                        UserAreaDTO.builder().name(item.getKey()).value(item.getValue()).build()
                )
                .collect(Collectors.toList());
        redisService.set(USER_AREA, JSON.toJSONString(userAreaList));
    }

    /**
     * 登录（更新用户信息）
     *
     * @return
     */
    private UserInfoDTO updateUserInfo(UserAuth user, String ipAddress, String ipSource) {
        //查询、更新并返回新的用户细节信息
        userAuthDao.update(new UserAuth(), new LambdaUpdateWrapper<UserAuth>()
                .set(UserAuth::getIpAddress, ipAddress)
                .set(UserAuth::getIpSource, ipSource)
                .set(UserAuth::getLastLoginTime, LocalDateTime.now())
                .eq(UserAuth::getId, user.getId()));
        UserDetailDTO userDetailDTO = userDetailsService.convertUserDetail(user, request);

        // 将登录信息放入springSecurity管理
        UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userDetailDTO, null, userDetailDTO.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(auth);
        sessionRegistry.registerNewSession(user.getId().toString(),auth.getPrincipal());
        //List<Object> allPrincipals = sessionRegistry.getAllPrincipals();
        //返回用户信息
        return BeanCopyUtils.copyObject(userDetailDTO, UserInfoDTO.class);
    }

    /**
     * 微信快捷登录（保存用户信息）
     *
     * @return
     */
    private UserInfoDTO saveUserInfo(WeiXinLoginVO weiXinLoginVO, String ipAddress, String ipSource) {
        //1、新增用户信息
        UserInfo userInfo = UserInfo.builder()
                .nickname(weiXinLoginVO.getNickName())
                .avatar(weiXinLoginVO.getAvatar())
                .createTime(LocalDateTime.now())
                .build();
        userInfoDao.insert(userInfo);
        //2、新增账号信息
        UserAuth userAuth = UserAuth.builder()
                .userInfoId(userInfo.getId())
                .username(weiXinLoginVO.getOpenId())
                .password(BCrypt.hashpw(weiXinLoginVO.getOpenId(),BCrypt.gensalt()))
                .loginType(LoginTypeEnum.WEIXIN.getType())
                .ipAddress(ipAddress).ipSource(ipSource)
                .createTime(LocalDateTime.now())
                .build();
        userAuthDao.insert(userAuth);
        //3、绑定角色
        UserRole userRole = UserRole.builder()
                .userId(userAuth.getId())
                .roleId(RoleEnum.USER.getRoleId())
                .build();
        userRoleDao.insert(userRole);
        UserDetailDTO userDetailDTO = userDetailsService.convertUserDetail(userAuth, request);
        if (userDetailDTO.getIsDisable() == TRUE) {
            throw new BizException("账号已被禁用");
        }
        // 将登录信息放入springSecurity管理
        UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userDetailDTO, null, userDetailDTO.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(auth);
        sessionRegistry.registerNewSession(userAuth.getId().toString(),auth.getPrincipal());
        //返回用户信息
        return BeanCopyUtils.copyObject(userDetailDTO, UserInfoDTO.class);
    }


    /**
     * 手机号登录（保存用户信息）
     *
     * @return
     */
    private UserInfoDTO saveUserInfo(UserPhoneLoginVO userPhoneLoginVO, String ipAddress, String ipSource) {
        //1、新增用户信息
        UserInfo userInfo = UserInfo.builder()
                .nickname(DEFAULT_NICKNAME  + IdWorker.getId())
                .avatar(DEFAULT_AVATAR_URL)
                .phone(userPhoneLoginVO.getPhone())
                .createTime(LocalDateTime.now())
                .build();
        userInfoDao.insert(userInfo);
        //2、新增账号信息
        UserAuth userAuth = UserAuth.builder()
                .userInfoId(userInfo.getId())
                .username(userPhoneLoginVO.getPhone())
                .password(BCrypt.hashpw(userPhoneLoginVO.getPhone(),BCrypt.gensalt()))
                .phone(userPhoneLoginVO.getPhone())
                .loginType(LoginTypeEnum.PHONE.getType())
                .ipAddress(ipAddress).ipSource(ipSource)
                .createTime(LocalDateTime.now())
                .build();
        userAuthDao.insert(userAuth);
        //3、绑定角色
        UserRole userRole = UserRole.builder()
                .userId(userAuth.getId())
                .roleId(RoleEnum.USER.getRoleId())
                .build();
        userRoleDao.insert(userRole);
        UserDetailDTO userDetailDTO = userDetailsService.convertUserDetail(userAuth, request);
        if (userDetailDTO.getIsDisable() == TRUE) {
            throw new BizException("账号已被禁用");
        }
        // 将登录信息放入springSecurity管理
        UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userDetailDTO, null, userDetailDTO.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(auth);
        sessionRegistry.registerNewSession(userAuth.getId().toString(),auth.getPrincipal());
        //List<SessionInformation> allSessions = sessionRegistry.getAllSessions();
        //返回用户信息
        return BeanCopyUtils.copyObject(userDetailDTO, UserInfoDTO.class);
    }

    //    @Transactional(rollbackFor = Exception.class)
//    @Override
//    public UserInfoDTO qqLogin(QQLoginVO qqLoginVO) {
//        return socialLoginStrategyContext.executeLoginStrategy(JSON.toJSONString(qqLoginVO), LoginTypeEnum.QQ);
//    }
//
//    @Transactional(rollbackFor = BizException.class)
//    @Override
//    public UserInfoDTO weiboLogin(WeiboLoginVO weiboLoginVO) {
//        return socialLoginStrategyContext.executeLoginStrategy(JSON.toJSONString(weiboLoginVO), LoginTypeEnum.WEIBO);
//    }

}
